﻿using System;
//using t = System.Tuple<int>;
using System.Collections.Generic;
using System.IO;
using System.Linq;
//using System.Net;
//using System.Net.Sockets;
using System.Security;
using System.Text;
using System.Windows.Forms;
using Lextm.SharpSnmpLib;
using Lextm.SharpSnmpLib.Messaging;
using Lextm.SharpSnmpLib.Security;
using monitor_2_DBwork;
using System.Net;
using System.Net.Sockets;

namespace ServerLib.Entities
{
    public delegate void GetNetworkDelegate(IPAddress a);
    static class SnmpTrapsListener
    {

        
        private static IPAddress _networkIp;
        private static List<ConnectionInfo> _ciList = new List<ConnectionInfo>(); 

        private class ConnectionInfo
        {
            public Socket soc;
            public byte[] buf = new byte[10000];
        }
        
        //private static Listener _listener;
        
        //private static List<string[]> _devices;
        private static Dictionary<IPAddress, DllUnmanagedWork> _dll; 

        public static string Start()
        {
            var devices = DBwork.GetDeviceLibsList();
            // 0 - ip
            // 1 - lib name
            // 2 - dev name
            // 3 - rtps name
            _dll = new Dictionary<IPAddress, DllUnmanagedWork>();
            foreach (var device in devices)
            {
                //var d = new DllUnmanagedWork(device[1], device[2]);
                //_dll.Add(IPAddress.Parse(device[0]), d);
                _dll.Add(IPAddress.Parse(device[0]), Server.DeviceLibraries.GetByDeviceName(device[2]));
            }

            var con = new ConnectionInfo();
            try
            {
                con.soc = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);
            }
            catch (SocketException ex)
            {
                return "err:" + ex.Message;
                    // + ". \r\nНевозможно запустить процесс прослушивания SNMP ловушек. \r\nВозможно не достаточно прав доступа.";
            }
            
            con.soc.ExclusiveAddressUse = false;

            var storedIp = DBwork.GetOptionValue(Server.CurrentIpToListenValueName);
            if (storedIp == "0.0.0.0")
                storedIp = "err:";
            if (storedIp.Contains("err:"))
            {
                //Выбор сокета, который нужно прослушивать на ловушки
                IPHostEntry hostEntry = Dns.GetHostEntry((Dns.GetHostName()));
                List<IPAddress> nList;
                GetNetworkDelegate gnd = ip => { _networkIp = ip; };
                if (hostEntry.AddressList.Length > 0)
                {
                    nList = new List<IPAddress>(hostEntry.AddressList.Count());
                    foreach (var ipAddress in hostEntry.AddressList)
                    {
                        if (!ipAddress.Equals(IPAddress.Parse("::1")))
                            nList.Add(ipAddress);
                    }
                }
                else
                {
                    return "err:" + Server.Rm.GetString("NoNetworks");
                }
                var tn = new TakeNetworkForm(nList, gnd);
                var res = tn.ShowDialog();
                if (res == DialogResult.Abort)
                {
                    return "err:" + Server.Rm.GetString("UserAbort") + ": SNMP traps listening.";
                }
                var chk = DBwork.AddOrSetOptionValue(Server.CurrentIpToListenValueName, _networkIp.ToString());
                if (chk.Contains("err:"))
                    return chk;
            }
            else
            {
                _networkIp = IPAddress.Parse(storedIp);
            }

            try
            {
                con.soc.Bind(new IPEndPoint(_networkIp, 162));
            }
            catch (SocketException ex)
            {
                return "err:" + ex.Message;
            }
            
            con.soc.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, true);
            var byInc = new byte[] { 1, 0, 0, 0 };
            var byOut = new byte[] { 1, 0, 0, 0 };
            const IOControlCode sioRcvall = (IOControlCode)0x98000001;
            
            con.soc.IOControl(sioRcvall, byInc, byOut);
            
            _ciList.Add(con);
            con.soc.BeginReceive(con.buf, 0, con.buf.Length, SocketFlags.None, RequestCallback, con);

            return "ok";

        }

        private static void RequestCallback(IAsyncResult ar)
        {
            var date = DateTime.Now;
            var con = (ConnectionInfo)ar.AsyncState;

            try
            {
                var bytesrec = con.soc.EndReceive(ar);

                var buff = new byte[bytesrec];
                for (int i = 0; i < bytesrec; i++)
                {
                    buff[i] = con.buf[i];
                }
                IPAddress ip;
                var d = ParseData(buff, bytesrec, out ip);
                if (d.Length == 1 && d[0] == 0)
                {
                    con.soc.BeginReceive(con.buf, 0, con.buf.Length, SocketFlags.None, RequestCallback, con);
                    return;
                }
                var mes = MessageFactory.ParseMessages(d, new UserRegistry());
                var trap = mes[0] as TrapV2Message;
                if (trap == null)
                    return;
                var device = DBwork.GetDeviceIps();
                string check = "ok";
                if (device.ContainsKey(ip))
                    check = _dll[ip].TrapHandle(device[ip].ToString(), trap, date);
                if (check != "ok")
                {
                    Server.WriteToLog(check, ServerCategories.Warning);
                }

            }
            catch (ObjectDisposedException)
            {
            }
            catch (Exception ex)
            {
                con.soc.BeginReceive(con.buf, 0, con.buf.Length, SocketFlags.None, RequestCallback, con);
            }

            

            //var str = Encoding.ASCII.GetString(buff);
            //con.soc.BeginReceive(con.buf, 0, con.buf.Length, SocketFlags.None, RequestCallback, con);

        }

        public static void Stop()
        {
            try
            {
                //_trapsListener.Shutdown(SocketShutdown.Both);
                //_trapsListener.Close();
                foreach (var connectionInfo in _ciList)
                {
                    connectionInfo.soc.Shutdown(SocketShutdown.Both);
                    connectionInfo.soc.Close();
                }
                _ciList.Clear();
            }
            catch (Exception)
            {
            }
        }

        private static byte[] ParseData(byte[] byteData, int nReceived, out IPAddress SourceIP)
        {

            //Since all protocol packets are encapsulated in the IP datagram
            //so we start by parsing the IP header and see what protocol data
            //is being carried by it
            var ipHeader = new IPHeader(byteData, nReceived);


            //Now according to the protocol being carried by the IP datagram we parse 
            //the data field of the datagram
            switch (ipHeader.ProtocolType)
            {
                case Protocol.UDP:

                    var udpHeader = new UDPHeader(ipHeader.Data,              //IPHeader.Data stores the data being 
                        //carried by the IP datagram
                                                       (int)ipHeader.MessageLength);//Length of the data field                    
                    
                    //If the port is equal to 53 then the underlying protocol is DNS
                    //Note: DNS can use either TCP or UDP thats why the check is done twice
                    if (udpHeader.DestinationPort == "53" || udpHeader.SourcePort == "53")
                    {
                        //dns
                        break;
                    }
                    if (udpHeader.DestinationPort != "162")
                        break;
                    int c = int.Parse(udpHeader.Length);
                    var ret = new byte[c-8];
                    Array.Copy(udpHeader.Data,ret,c-8);
                    SourceIP = ipHeader.SourceAddress;
                    return ret;
                    //return udpHeader.Data;

                case Protocol.Unknown:
                    break;
            }
            SourceIP = IPAddress.Any;
            return new byte[] {0};

        }
    }

    public class IPHeader
    {
        //IP Header fields
        private byte byVersionAndHeaderLength;   //Eight bits for version and header length
        private byte byDifferentiatedServices;    //Eight bits for differentiated services (TOS)
        private ushort usTotalLength;              //Sixteen bits for total length of the datagram (header + message)
        private ushort usIdentification;           //Sixteen bits for identification
        private ushort usFlagsAndOffset;           //Eight bits for flags and fragmentation offset
        private byte byTTL;                      //Eight bits for TTL (Time To Live)
        private byte byProtocol;                 //Eight bits for the underlying protocol
        private short sChecksum;                  //Sixteen bits containing the checksum of the header
        //(checksum can be negative so taken as short)
        private uint uiSourceIPAddress;          //Thirty two bit source IP Address
        private uint uiDestinationIPAddress;     //Thirty two bit destination IP Address
        //End IP Header fields

        private byte byHeaderLength;             //Header length
        private byte[] byIPData = new byte[4096];  //Data carried by the datagram


        public IPHeader(byte[] byBuffer, int nReceived)
        {

            try
            {
                //Create MemoryStream out of the received bytes
                MemoryStream memoryStream = new MemoryStream(byBuffer, 0, nReceived);
                //Next we create a BinaryReader out of the MemoryStream
                BinaryReader binaryReader = new BinaryReader(memoryStream);

                //The first eight bits of the IP header contain the version and
                //header length so we read them
                byVersionAndHeaderLength = binaryReader.ReadByte();

                //The next eight bits contain the Differentiated services
                byDifferentiatedServices = binaryReader.ReadByte();

                //Next eight bits hold the total length of the datagram
                usTotalLength = (ushort)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

                //Next sixteen have the identification bytes
                usIdentification = (ushort)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

                //Next sixteen bits contain the flags and fragmentation offset
                usFlagsAndOffset = (ushort)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

                //Next eight bits have the TTL value
                byTTL = binaryReader.ReadByte();

                //Next eight represnts the protocol encapsulated in the datagram
                byProtocol = binaryReader.ReadByte();

                //Next sixteen bits contain the checksum of the header
                sChecksum = IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

                //Next thirty two bits have the source IP address
                uiSourceIPAddress = (uint)(binaryReader.ReadInt32());

                //Next thirty two hold the destination IP address
                uiDestinationIPAddress = (uint)(binaryReader.ReadInt32());

                //Now we calculate the header length

                byHeaderLength = byVersionAndHeaderLength;
                //The last four bits of the version and header length field contain the
                //header length, we perform some simple binary airthmatic operations to
                //extract them
                byHeaderLength <<= 4;
                byHeaderLength >>= 4;
                //Multiply by four to get the exact header length
                byHeaderLength *= 4;

                //Copy the data carried by the data gram into another array so that
                //according to the protocol being carried in the IP datagram
                Array.Copy(byBuffer,
                           byHeaderLength,  //start copying from the end of the header
                           byIPData, 0,
                           usTotalLength - byHeaderLength);
            }
            catch (Exception ex)
            {
                //MessageBox.Show(ex.Message, "MJsniffer", MessageBoxButtons.OK,
                //    MessageBoxIcon.Error);
            }
        }

        public string Version
        {
            get
            {
                //Calculate the IP version

                //The four bits of the IP header contain the IP version
                if ((byVersionAndHeaderLength >> 4) == 4)
                {
                    return "IP v4";
                }
                else if ((byVersionAndHeaderLength >> 4) == 6)
                {
                    return "IP v6";
                }
                else
                {
                    return "Unknown";
                }
            }
        }

        public string HeaderLength
        {
            get
            {
                return byHeaderLength.ToString();
            }
        }

        public ushort MessageLength
        {
            get
            {
                //MessageLength = Total length of the datagram - Header length
                return (ushort)(usTotalLength - byHeaderLength);
            }
        }

        public string DifferentiatedServices
        {
            get
            {
                //Returns the differentiated services in hexadecimal format
                return string.Format("0x{0:x2} ({1})", byDifferentiatedServices,
                    byDifferentiatedServices);
            }
        }

        public string Flags
        {
            get
            {
                //The first three bits of the flags and fragmentation field 
                //represent the flags (which indicate whether the data is 
                //fragmented or not)
                int nFlags = usFlagsAndOffset >> 13;
                if (nFlags == 2)
                {
                    return "Don't fragment";
                }
                else if (nFlags == 1)
                {
                    return "More fragments to come";
                }
                else
                {
                    return nFlags.ToString();
                }
            }
        }

        public string FragmentationOffset
        {
            get
            {
                //The last thirteen bits of the flags and fragmentation field 
                //contain the fragmentation offset
                int nOffset = usFlagsAndOffset << 3;
                nOffset >>= 3;

                return nOffset.ToString();
            }
        }

        public string TTL
        {
            get
            {
                return byTTL.ToString();
            }
        }

        public Protocol ProtocolType
        {
            get
            {
                //The protocol field represents the protocol in the data portion
                //of the datagram
                if (byProtocol == 6)        //A value of six represents the TCP protocol
                {
                    return Protocol.TCP;
                }
                else if (byProtocol == 17)  //Seventeen for UDP
                {
                    return Protocol.UDP;
                }
                else
                {
                    return Protocol.Unknown;
                }
            }
        }

        public string Checksum
        {
            get
            {
                //Returns the checksum in hexadecimal format
                return string.Format("0x{0:x2}", sChecksum);
            }
        }

        public IPAddress SourceAddress
        {
            get
            {
                return new IPAddress(uiSourceIPAddress);
            }
        }

        public IPAddress DestinationAddress
        {
            get
            {
                return new IPAddress(uiDestinationIPAddress);
            }
        }

        public string TotalLength
        {
            get
            {
                return usTotalLength.ToString();
            }
        }

        public string Identification
        {
            get
            {
                return usIdentification.ToString();
            }
        }

        public byte[] Data
        {
            get
            {
                return byIPData;
            }
        }
    }

    public class TCPHeader
    {
        //TCP header fields
        private ushort usSourcePort;              //Sixteen bits for the source port number
        private ushort usDestinationPort;         //Sixteen bits for the destination port number
        private uint uiSequenceNumber = 555;          //Thirty two bits for the sequence number
        private uint uiAcknowledgementNumber = 555;   //Thirty two bits for the acknowledgement number
        private ushort usDataOffsetAndFlags = 555;      //Sixteen bits for flags and data offset
        private ushort usWindow = 555;                  //Sixteen bits for the window size
        private short sChecksum = 555;                 //Sixteen bits for the checksum
        //(checksum can be negative so taken as short)
        private ushort usUrgentPointer;           //Sixteen bits for the urgent pointer
        //End TCP header fields

        private byte byHeaderLength;            //Header length
        private ushort usMessageLength;           //Length of the data being carried
        private byte[] byTCPData = new byte[4096];//Data carried by the TCP packet

        public TCPHeader(byte[] byBuffer, int nReceived)
        {
            try
            {
                MemoryStream memoryStream = new MemoryStream(byBuffer, 0, nReceived);
                BinaryReader binaryReader = new BinaryReader(memoryStream);

                //The first sixteen bits contain the source port
                usSourcePort = (ushort)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

                //The next sixteen contain the destiination port
                usDestinationPort = (ushort)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

                //Next thirty two have the sequence number
                uiSequenceNumber = (uint)IPAddress.NetworkToHostOrder(binaryReader.ReadInt32());

                //Next thirty two have the acknowledgement number
                uiAcknowledgementNumber = (uint)IPAddress.NetworkToHostOrder(binaryReader.ReadInt32());

                //The next sixteen bits hold the flags and the data offset
                usDataOffsetAndFlags = (ushort)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

                //The next sixteen contain the window size
                usWindow = (ushort)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

                //In the next sixteen we have the checksum
                sChecksum = (short)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

                //The following sixteen contain the urgent pointer
                usUrgentPointer = (ushort)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

                //The data offset indicates where the data begins, so using it we
                //calculate the header length
                byHeaderLength = (byte)(usDataOffsetAndFlags >> 12);
                byHeaderLength *= 4;

                //Message length = Total length of the TCP packet - Header length
                usMessageLength = (ushort)(nReceived - byHeaderLength);

                //Copy the TCP data into the data buffer
                Array.Copy(byBuffer, byHeaderLength, byTCPData, 0, nReceived - byHeaderLength);
            }
            catch (Exception ex)
            {
                //MessageBox.Show(ex.Message, "MJsniff TCP" + (nReceived), MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        public string SourcePort
        {
            get
            {
                return usSourcePort.ToString();
            }
        }

        public string DestinationPort
        {
            get
            {
                return usDestinationPort.ToString();
            }
        }

        public string SequenceNumber
        {
            get
            {
                return uiSequenceNumber.ToString();
            }
        }

        public string AcknowledgementNumber
        {
            get
            {
                //If the ACK flag is set then only we have a valid value in
                //the acknowlegement field, so check for it beore returning 
                //anything
                if ((usDataOffsetAndFlags & 0x10) != 0)
                {
                    return uiAcknowledgementNumber.ToString();
                }
                else
                    return "";
            }
        }

        public string HeaderLength
        {
            get
            {
                return byHeaderLength.ToString();
            }
        }

        public string WindowSize
        {
            get
            {
                return usWindow.ToString();
            }
        }

        public string UrgentPointer
        {
            get
            {
                //If the URG flag is set then only we have a valid value in
                //the urgent pointer field, so check for it beore returning 
                //anything
                if ((usDataOffsetAndFlags & 0x20) != 0)
                {
                    return usUrgentPointer.ToString();
                }
                else
                    return "";
            }
        }

        public string Flags
        {
            get
            {
                //The last six bits of the data offset and flags contain the
                //control bits

                //First we extract the flags
                int nFlags = usDataOffsetAndFlags & 0x3F;

                string strFlags = string.Format("0x{0:x2} (", nFlags);

                //Now we start looking whether individual bits are set or not
                if ((nFlags & 0x01) != 0)
                {
                    strFlags += "FIN, ";
                }
                if ((nFlags & 0x02) != 0)
                {
                    strFlags += "SYN, ";
                }
                if ((nFlags & 0x04) != 0)
                {
                    strFlags += "RST, ";
                }
                if ((nFlags & 0x08) != 0)
                {
                    strFlags += "PSH, ";
                }
                if ((nFlags & 0x10) != 0)
                {
                    strFlags += "ACK, ";
                }
                if ((nFlags & 0x20) != 0)
                {
                    strFlags += "URG";
                }
                strFlags += ")";

                if (strFlags.Contains("()"))
                {
                    strFlags = strFlags.Remove(strFlags.Length - 3);
                }
                else if (strFlags.Contains(", )"))
                {
                    strFlags = strFlags.Remove(strFlags.Length - 3, 2);
                }

                return strFlags;
            }
        }

        public string Checksum
        {
            get
            {
                //Return the checksum in hexadecimal format
                return string.Format("0x{0:x2}", sChecksum);
            }
        }

        public byte[] Data
        {
            get
            {
                return byTCPData;
            }
        }

        public ushort MessageLength
        {
            get
            {
                return usMessageLength;
            }
        }
    }

    public class UDPHeader
    {
        //UDP header fields
        private ushort usSourcePort;            //Sixteen bits for the source port number        
        private ushort usDestinationPort;       //Sixteen bits for the destination port number
        private ushort usLength;                //Length of the UDP header
        private short sChecksum;                //Sixteen bits for the checksum
        //(checksum can be negative so taken as short)              
        //End UDP header fields

        private byte[] byUDPData = new byte[4096];  //Data carried by the UDP packet

        public UDPHeader(byte[] byBuffer, int nReceived)
        {
            MemoryStream memoryStream = new MemoryStream(byBuffer, 0, nReceived);
            BinaryReader binaryReader = new BinaryReader(memoryStream);

            //The first sixteen bits contain the source port
            usSourcePort = (ushort)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

            //The next sixteen bits contain the destination port
            usDestinationPort = (ushort)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

            //The next sixteen bits contain the length of the UDP packet
            usLength = (ushort)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

            //The next sixteen bits contain the checksum
            sChecksum = IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

            //Copy the data carried by the UDP packet into the data buffer
            Array.Copy(byBuffer,
                       8,               //The UDP header is of 8 bytes so we start copying after it
                       byUDPData,
                       0,
                       nReceived - 8);
        }

        public string SourcePort
        {
            get
            {
                return usSourcePort.ToString();
            }
        }

        public string DestinationPort
        {
            get
            {
                return usDestinationPort.ToString();
            }
        }

        public string Length
        {
            get
            {
                return usLength.ToString();
            }
        }

        public string Checksum
        {
            get
            {
                //Return the checksum in hexadecimal format
                return string.Format("0x{0:x2}", sChecksum);
            }
        }

        public byte[] Data
        {
            get
            {
                return byUDPData;
            }
        }
    }

    public enum Protocol
    {
        TCP = 6,
        UDP = 17,
        Unknown = -1
    };
}
